package moodish.dummy;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;

import moodish.comm.ClientSideMessage.Type;
import moodish.comm.MessageFromServer;
import moodish.comm.MessageToServer;
import moodish.comm.ServerComm;
import moodish.comm.ServerSideMessage;
import moodish.comm.DealWithClient;

public class ServerCommDummy implements ServerComm {
	public static final int PORT = 8085;

	// The Channels for each client
	private HashMap<String, DealWithClient> clients = new HashMap<String, DealWithClient>();
	// The messages received
	private LinkedList<MessageToServer> messages = new LinkedList<MessageToServer>();
	// The server socket
	private ServerSocket serverSocket;

	/**
	 * Start the modish server
	 * Creates a server socket and start accepting connections from users
	 */
	@Override
	public void start() {
		// Lista de todos os canais de cliente
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					// Creating server on the PORT
					serverSocket = new ServerSocket(PORT);
					while (true) {
						// Blocks until someone try to connect
						// Accept the socket
						Socket socket = serverSocket.accept();
						
						// Create the comunication channels
						// In and OutPutObject's
						
						ObjectInputStream in = new ObjectInputStream(
								socket.getInputStream());

						ObjectOutputStream out = new ObjectOutputStream(
								socket.getOutputStream());

						try {
							// 	Read the connection Request
							MessageToServer msg = (MessageToServer) in.readObject();
							// Accept Client
							acceptClient(socket, in, out, msg);
							
						} catch (ClassNotFoundException e) {
							// Error while recebing some message from the client
						}
						
						// Everything allright, accept new clients
					}

				} catch (IOException ex) {
					// Some error occured, the connections will be all shut down
				} finally {
					try {
						// Try to close the serverSocket
						serverSocket.close();
					} catch (IOException e) {
						// Error - Could not close the server socket
					}
				}
			}
		}).start();
	}

	/**
	 * Allow to check if there is some unread message
	 * 
	 * @return boolean 
	 */
	@Override
	public boolean hasNextMessage() {
		// Tell if there is some message on the list
		return (messages.size() > 0);
	}

	/**
	 * Allow to first unread message received
	 * 
	 * @return SeverSideMessage
	 */
	@Override
	public synchronized ServerSideMessage getNextMessage() {
		// Return the first message received
		try {
			while (!hasNextMessage()) {
				wait();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		MessageToServer msg = messages.getFirst();
		messages.removeFirst();

		return msg;
	}

	/**
	 * Send New Friendship to
	 * 
	 * @param String toNickname client nickname to receive friendship
	 * @param String newFriendShip client nickname who ask for friendship
	 */
	@Override
	public void sendNewFriendship(String toNickname, String newFriendship) {
		// Send New Friendship from, to
		MessageFromServer msg = new MessageFromServer(Type.FRIENDSHIP,
				newFriendship);
		sendMessageTo(clients.get(toNickname), msg);
	}

	/**
	 * Send error to a client
	 * 
	 * @param String toNickname nickname where should be send the error message
	 * @param String error message
	 */
	@Override
	public void sendError(String toNickname, String error) {
		// Some error occured. Send it to toNickname
		MessageFromServer msg = new MessageFromServer(Type.ERROR, error);
		sendMessageTo(clients.get(toNickname), msg);
	}

	/**
	 * Confirm the connectivity of a client based on his nickname
	 * 
	 * @param String nickname client who is supose to confirm connectivity
	 * @return boolean true if the client is currently connected
	 */
	@Override
	public boolean clientIsConnected(String nickname) {
		// Tell if the nickname is currently connected
		return clients.get(nickname).isConnect();
	}
	
	/**
	 * Send client connected to 
	 * 
	 * @param String toNickname nickname target
	 * @param userConnected connected ser nickname to be send to the target
	 */
	@Override
	public void sendClientConnected(String toNickname, String userConnected) {
		// Say the client is now connected
		MessageFromServer msg = new MessageFromServer(Type.CONNECTED,
				userConnected);
		sendMessageTo(clients.get(toNickname), msg);
	}

	/**
	 * Send client disconnected to 
	 * 
	 * @param String toNickname nickname target
	 * @param userConnected disconnected user nickname to be send to the target 
	 */
	@Override
	public void sendClientDisconnected(String toNickname,
			String userDisconnected) {
		// Say the client have been disconnected
		MessageFromServer msg = new MessageFromServer(Type.DISCONNECTED,
				userDisconnected);
		sendMessageTo(clients.get(toNickname), msg);
	}

	/**
	 * Disconnect the client
	 * 
	 * @param String client which will be disconnect
	 */
	@Override
	public void disconnectClient(String nickname) {
		// Shut down everything
		// Disconnect client
		clients.get(nickname).shutDown();
		clients.remove(nickname);
	}
	/**
	 * Moodish message sent from 
	 * 
	 * @param String fromNickname owner of the moodish
	 * @param String toNickname client who will receive the moodish
	 * @param String message moodish message sent
	 */
	@Override
	public void sendMoodishMessage(String fromNicename, String toNickname,
			String message) {
		// Send message to
		MessageFromServer msg = new MessageFromServer(Type.MOODISH_MESSAGE,
				fromNicename, message);
		sendMessageTo(clients.get(toNickname), msg);
	}

	/**
	 * Send new unfriend for 
	 * 
	 * @param String toNickname client to receive the message
	 * @param String newUnfriendship client who unfriendship the first
	 */
	@Override
	public void sendNewUnfriendship(String toNickname, String newUnfriendship) {
		// Send Unfriendship
		MessageFromServer msg = new MessageFromServer(Type.UNFRIENDSHIP,
				newUnfriendship);
		sendMessageTo(clients.get(toNickname), msg);
	}

	/**
	 * Sends a specific message in a certain Channel output
	 * 
	 * @param out Channel to send the Message
	 * @param msg Message to be sent for client
	 */
	public void sendMessageTo(DealWithClient dealer, MessageFromServer msg) {
		ObjectOutputStream out;
		try {
			out = dealer.getOutputStream();
			out.writeObject(msg);
		} catch (IOException e) { 
			
		} catch (NullPointerException e) {
			
		}
		
	}

	/**
	 * Add a MessageToServer readed from
	 * some DealWithClient and add it to the
	 * end of the lsit 
	 * 
	 * @param msg MessageToServer 
	 */
	public synchronized void addMessage(MessageToServer msg) {
		messages.addLast(msg);
		notifyAll();
	}
	
	/**
	 * Accept a new client
	 * Don't if the nick already exits
	 * @param socket 
	 * 
	 * 
	 * @param in
	 * @param out
	 * @param nick
	 */
	private void acceptClient(Socket socket, ObjectInputStream in, ObjectOutputStream out, MessageToServer msg) {
		DealWithClient dealer = new DealWithClient(in, this, socket, out);
		
		try {
			// If the nickname already exits throw new Exception
			if (clients.keySet().contains(msg.getClientNickname())) {
				throw new Exception();
			}
			// If don't put on the map with his dealer (DealWithClient)
			clients.put(msg.getClientNickname(), dealer);
			
			// Add connection message to server & start the dealer
			addMessage(msg);
			dealer.start();
		} catch (Exception e) {
			// Send a MessageFromServer with an Error telling this nickname is duplicated.
			MessageFromServer msgFS = new MessageFromServer(Type.ERROR, "Nickname duplicated. Please pick another one");
			sendMessageTo(dealer, msgFS);
			
			// Shut down this dealer
			dealer.shutDown();
		}
	}

	/**
	 * Remove a specific DealWithClient
	 * Used when some connection broke up
	 * 
	 * Send a message to the server telling the <nickname> disconnect 
	 * 
	 * @param dealWithClient to be removed
	 */
	public void removeClient(DealWithClient dealWithClient) {		
		for (String key: clients.keySet()) {
			if (clients.get(key).equals(dealWithClient)) {
				// Send message with disconnect to server
				MessageToServer msg = new MessageToServer(key, null, ServerSideMessage.Type.CLIENT_DISCONNECTED);
				addMessage(msg);
				
				// Shutdown everything just in case
				clients.get(key).shutDown();
				// Remove from the hashmap
				clients.remove(key);
			}
		}
	}
	
	public void shutDownServer() {
		try {
			System.out.println("Closing Server Socket - ServerCommDummy");
			serverSocket.close();
		}
		catch (IOException e) {
			System.out.println("IO Error - ServerCommDummy - shutdown");
		}
	}
}
